{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# The infamously simple [FizzBuzz](https://en.wikipedia.org/wiki/Fizz_buzz#Programming_interviews) problem.\n",
    "\n",
    "Reportedly a high percentage of programmer applicants can't solve this quickly.\n",
    "\n",
    "> Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”.\n",
    "\n",
    "A deep dive on this problem has been done in jest many times, e.g., deliberate over-engineering or [code golf](https://en.wikipedia.org/wiki/Code_golf).  But in all seriousness, let's consider what's the most Pythonic solution.  A truncated version of the common solution: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": "1\n2\nFizz\n4\nBuzz\nFizz\n7\n8\nFizz\nBuzz\n11\nFizz\n13\n14\nFizzBuzz\n"
    }
   ],
   "source": [
    "for num in range(1, 16):\n",
    "    if num % 5 == 0 and num % 3 == 0:\n",
    "        print('FizzBuzz')\n",
    "    elif num % 3 == 0:\n",
    "        print('Fizz')\n",
    "    elif num % 5 == 0:\n",
    "        print('Buzz')\n",
    "    else:\n",
    "        print(num)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Naturally interview questions tend to focus on output, e.g. `print`, but that's no reason to skip over basic abstractions or data structures.  First, this could be written as a generator, to decouple the `print` operation and parametrize the numeric range.  Alternatively, Python has such strong iterator support that it could also be just a function, ready to be mapped.  So let's reframe the basic solution as:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": "'1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz'"
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def fizzbuzz(stop):\n",
    "    for num in range(1, stop):\n",
    "        if num % 5 == 0 and num % 3 == 0:\n",
    "            yield 'FizzBuzz'\n",
    "        elif num % 3 == 0:\n",
    "            yield 'Fizz'\n",
    "        elif num % 5 == 0:\n",
    "            yield 'Buzz'\n",
    "        else: \n",
    "            yield str(num)\n",
    "\n",
    "' '.join(fizzbuzz(16))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Even at this size, it's already violating [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself), or the [Rule of 3](https://en.wikipedia.org/wiki/Rule_of_three_%28computer_programming%29).  Clearly the same logic is being repeated with different data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": "'1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz'"
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def fizzbuzz(stop):\n",
    "    items = (15, 'FizzBuzz'), (3, 'Fizz'), (5, 'Buzz')\n",
    "    for num in range(1, stop):\n",
    "        yield next((text for div, text in items if num % div == 0), str(num))\n",
    "\n",
    "' '.join(fizzbuzz(16))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "However, that variation had to introduce the concept of the [least common multiple](https://en.wikipedia.org/wiki/Least_common_multiple).  Even in such a trivial problem, there's a subtlety in how one interprets requirements.  The final directive to output \"FizzBuzz\" can be seen as a mere clarification of the previous directives; certainly not a coincidence.  Making this the more obvious solution:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": "'1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz'"
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def fizzbuzz(stop):\n",
    "    for num in range(1, stop):\n",
    "        text = ''\n",
    "        if num % 3 == 0:\n",
    "            text += 'Fizz'\n",
    "        if num % 5 == 0:\n",
    "            text += 'Buzz'\n",
    "        yield text or str(num)\n",
    "\n",
    "' '.join(fizzbuzz(16))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Arguably that insight is more important, because its duplication grows exponentially, not linearly.  There's a `2**N` sized case statement to handle `N` cases, luckily `N == 2`.  Adding just one more directive for the number 7 would make the basic solution unwieldy.\n",
    "\n",
    "And of course both approaches can be combined."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": "'1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz'"
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def fizzbuzz(stop):\n",
    "    items = (3, 'Fizz'), (5, 'Buzz')\n",
    "    for num in range(1, stop):\n",
    "        yield ''.join(text for div, text in items if num % div == 0) or str(num)\n",
    "\n",
    "' '.join(fizzbuzz(16))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "So is that over-engineered?  This author would argue that both deduplication and decoupling logic from data are worth observing.  So maybe at this size the final version isn't necessary, but surely the basic version is not the most Pythonic."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "env": {},
   "interrupt_mode": "signal",
   "language": "python",
   "metadata": {},
   "name": "python3"
  },
  "nikola": {
   "category": "interviews",
   "date": "2018-03-25",
   "description": "",
   "link": "",
   "slug": "fizz-buzz",
   "tags": "",
   "title": "Fizz Buzz",
   "type": "text"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}